#include "Parser.h"

/* define the keys */
const char __Materials_block_start[] = "[Materials]";
const char __block_end[] = "[]";

const char __sub_start[] = "[./";
const char __sub_end[] = "[../]";
const char __sigma_t[] = "sigma_t";
const char __sigma_f[] = "sigma_f";
const char __sigma_a[] = "sigma_a";
const char __sigma_s[] = "sigma_s";
const char __chi[] = "chi";
const char __nu[] = "nu";
const char __id[] = "id";
const char __up[] = "up_scatter";
const char __down[] = "down_scatter";
const char __groups[] = "groups";
const char __density[] = "density";
const char __energy_center[] = "energy_center";
const char __energy_width[] = "energy_width";
const char __scattering_matrix[] = "scattering_matrix";

/* delimeters */
const char __character[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._";
const char __newline[] = "\n";
const char __delimeter[] = "=";
const char __left[] = "[";
const char __right[] = "]";
const char __comment[] = "#";


Parser::Parser()
{
    _file_name = NULL;
    _file_out_name = NULL;
}


Parser::Parser(const char* file_name)
{
    open(file_name);
}


Parser::~Parser()
{
    if (!_materials.empty())
    {
        for (auto iter = _materials.begin(); iter != _materials.end(); iter++)
        {
            delete iter->second;
            // iter->first.clear();
        }
        _materials.clear();
    }

    if (!_blocks.empty())
    {
        // for (auto iter = _blocks.begin(); iter != _blocks.end(); iter++)
        // {
        //     iter->first.clear();
        //     iter->second.clear();
        // }
        _blocks.clear();
    }
}


void Parser::setName(const char* name)
{
    int length = strlen(name);
    _file_name = new char [length+1];
    strcpy(_file_name, name);
    _file_name[length] = '\0';
}


void Parser::open(const char* file_name)
{
    if (_file_name != NULL) delete [] _file_name;

    setName(file_name);

    _file.open(_file_name, std::ios_base::in);

    if (!_file.is_open())
        std::cerr << "error: could not open the file: \"" << _file_name << "\"." << std::endl;
}


void Parser::open(const char* name, const char* write)
{
    if (strcmp(write, "write") == 0)
    {
        /* set the output file name */
        int length = strlen(name);
        _file_out_name = new char [length+1];
        strcpy(_file_out_name, name);
        _file_out_name[length] = '\0';

        _out.open(_file_out_name, std::ios_base::out);
    }
    else
    {
        std::cerr << "error: unknown output option " << write << "." << std::endl;
    }
}


void Parser::parse()
{
    /* read the full text to a string */
    _file.seekg(0, _file.end);
    int length = _file.tellg();
    _file.seekg(0, _file.beg);

    char* buffer = new char [length];
    _file.read(buffer, length);
    std::string full = std::string(buffer);

    std::cout << full;
    delete [] buffer;
    _file.close();

    /* remove the comments in the full text */
    std::string::size_type found_comment = full.find_first_of(__comment);
    while (found_comment != std::string::npos)
    {
        std::string::size_type old = found_comment;
        found_comment = full.find_first_of(__newline, found_comment+1);
        full.erase(old, found_comment-old);
        found_comment = full.find_first_of(__comment);
    }

    /* find the blocks */
    /* find block Materials [Materials] ... [] */
    findBlock(full, __Materials_block_start, __block_end);
    parseMaterials();
}


void Parser::findBlock(std::string &full, const char* start_label, const char* end_label)
{
    int begin = full.find(start_label);
    int end = full.find(end_label);
    std::string block = full.substr(begin, end-begin+strlen(end_label));
    full.erase(begin, end-begin+strlen(end_label));
    _blocks.insert(std::pair<std::string, std::string>(std::string(start_label), block));
}


void Parser::parseMaterials()
{
    if (_blocks.find(std::string(__Materials_block_start)) == _blocks.end())
        std::cerr << "error: there has no Materials block in the file." << std::endl;

    std::string materials = _blocks[std::string(__Materials_block_start)];

    std::string::size_type found_start = materials.find(__sub_start);
    std::string::size_type found_end;

    while (found_start != std::string::npos)
    {
        found_end = materials.find(__sub_end);
        std::string material = materials.substr(found_start, found_end-found_start+strlen(__sub_end));
        materials.erase(found_start, found_end-found_start+strlen(__sub_end));

        std::string name = getSubName(material);
        std::string id = getValue(__id, material);
        std::string sigma_t = getValue(__sigma_t, material);
        std::string sigma_f = getValue(__sigma_f, material);
        std::string sigma_s = getValue(__sigma_s, material);
        std::string sigma_a = getValue(__sigma_a, material);
        std::string groups = getValue(__groups, material);
        std::string up = getValue(__up, material);
        std::string down = getValue(__down, material);
        std::string chi = getValue(__chi, material);
        std::string nu = getValue(__nu, material);
        std::string density = getValue(__density, material);
        std::string energy_center = getValue(__energy_center, material);
        std::string energy_width = getValue(__energy_width, material);
        std::string scattering_matrix = getValue(__scattering_matrix, material);
        if (scattering_matrix == "")
        {
            // std::cout << "scattering_matrix" << std::endl;
            scattering_matrix = "upper";
        }

        Material* m = new Material();
        m->setName(str2str(name));
        m->setID(str2is(id));
        m->setGroups(str2is(groups));

        m->initialize();

        m->setUp(str2is(up));
        m->setDown(str2is(down));
        m->setDensity(str2ds(density));
        m->setSigma_t(str2ds(sigma_t));
        m->setSigma_f(str2ds(sigma_f));
        m->setSigma_s(str2ds(sigma_s), scattering_matrix);
        m->setSigma_a(str2ds(sigma_a));
        m->setChi(str2ds(chi));
        m->setNu(str2ds(nu));
        m->setEnergyCenter(str2ds(energy_center));
        m->setEnergyWidth(str2ds(energy_width));

        if (_fmt == XML)
            m->convert2Macro();

        _materials.insert(std::pair<std::string, Material*>(str2str(name), m));

        found_start = materials.find(__sub_start);
    }
}


std::string Parser::getSubName(std::string &block)
{
    std::string::size_type found_start = block.find(__sub_start);
    std::string::size_type found_end = block.find(__right);
    std::string name = block.substr(found_start+strlen(__sub_start),
                                    found_end-found_start-strlen(__sub_start)+1);
    return name;
}


std::string Parser::getValue(const char* key, std::string &block)
{
    std::string::size_type found_start;
    std::string::size_type found_end;

    std::string value;

    found_start = block.find(key);
    if (found_start >= std::string::npos)
    {
        std::cerr << "warning: could not find key \"" << key << "\"." << std::endl;
        return value;
    }

    found_start = block.find_first_of(__delimeter, found_start+1);
    found_start = block.find_first_of(__character, found_start+1);
    found_end = block.find_first_of(__newline, found_start+1);

    if (found_end < found_start)
    {
        std::cerr << "warning: the key \"" << key << "\" has No value" << std::endl;
        return value;
    }
    value = block.substr(found_start, found_end-found_start+1);

    return value;
}


std::vector<double> Parser::str2ds(std::string string)
{
    std::stringstream ss;
    std::vector<double> v;

    if (string.length() <= 0)
        return v;

    ss << string;
    double value;
    while(ss >> value)
    {
        v.push_back(value);
    }
    return v;
}


std::vector<int> Parser::str2is(std::string string)
{
    std::stringstream ss;
    std::vector<int> v;

    if (string.length() <= 0)
        return v;

    ss << string;
    double value;
    while(ss >> value)
    {
        v.push_back(value);
    }
    return v;
}


std::string Parser::str2str(std::string string)
{
    if (string.length() <= 0)
        std::cerr << "error: str2str(), string length is no more than 0." << std::endl;

    std::string::size_type found_start = string.find_first_of(__character);
    std::string::size_type found_end = string.find_last_of(__character);

    std::string str = string.substr(found_start, found_end-found_start+1);

    return str;
}


void Parser::write()
{
    if (!_out.is_open())
    {
        std::cerr << "error: could not open the output file." << std::endl;
        return;
    }

    switch(_fmt)
    {
        case MCNP:
        {
            for (auto iter = _materials.begin(); iter != _materials.end(); iter++)
                iter->second->write(_out, _fmt);

            break;
        }
        case XML:
        {
            /* add the header of xml file */
            _out << "<?xml version=\"1.0\"?>\n<materials>" << std::endl;
            for (auto iter = _materials.begin(); iter != _materials.end(); iter++)
            {
                iter->second->write(_out, _fmt);
                iter->second->print();
            }

            _out << "</materials>" << std::endl;
            break;
        }
    }

    _out.close();
}

void Parser::writeMOCP()
{
    FILE* fmocp = fopen(_mocp_m_file, "w");
    fprintf(fmocp, "[Materials]\n");
    fclose(fmocp);

    int id = INIT_MOCP_ID;

    for(auto iter = _materials.begin(); iter != _materials.end(); iter++)
    {
        iter->second->setID(id++);
        iter->second->write(_mocp_m_file);
    }

    fmocp = fopen(_mocp_m_file, "a");
    fprintf(fmocp, "[]\n");
    fclose(fmocp);
}
